package com.inspector.app.di.util;

import android.location.Location;

import com.inspector.app.util.ConstantUtil;
import com.inspector.app.util.LogUtil;
import com.mapsforge.map.EfbLatLngPoint;
import com.mapsforge.utils.TopoUtils;

import org.gdal.gdal.Band;
import org.gdal.gdal.Dataset;
import org.gdal.gdal.Driver;
import org.gdal.gdal.gdal;
import org.gdal.gdalconst.gdalconst;
import org.gdal.gdalconst.gdalconstConstants;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * https://gdal.org/
 * https://gdal.org/api/raster_c_api.html?highlight=applygeotransform
 * [GDAL读取珊瑚格数据]
 * <p>
 * 最好在IO线程中读取数据
 */
public class ElevationUtils {

    private String TAG = "ElevationUtils";
    private static volatile ElevationUtils elevationApi;

    public static ElevationUtils getInstance() {
        if (elevationApi == null) {
            synchronized (ElevationUtils.class) {
                if (elevationApi == null)
                    elevationApi = new ElevationUtils();
            }
        }
        return elevationApi;
    }

    //[gdal注册]
    public ElevationUtils() {
        gdal.AllRegister();
    }



    /**
     * 获取点的离线高程数据
     *
     * @param location 点经纬度
     * @return 返回有效的点经纬度对应的高程数据
     */
    public Double getOfflineElevation(EfbLatLngPoint location) {
        try {
            Dataset dataset = gdal.Open(ConstantUtil.tifFilePath, gdalconstConstants.GA_ReadOnly);
            if (dataset != null) {
                Driver driver = dataset.GetDriver();
                int RasterCount = dataset.getRasterCount();
//                LogUtil.e(TAG, "栅格数量：" + RasterCount);
                //构造仿射变换参数数组，并获取数据
                double[] gt = new double[6];
                dataset.GetGeoTransform(gt);

                //[点经纬度]
                double lat = location.lat;
                double lng = location.lng;

                double[] geoTransformsInDoubles = dataset.GetGeoTransform();
                Band rasterBand = dataset.GetRasterBand(1);
                double[] invGeoTransformsInDoubles = gdal.InvGeoTransform(geoTransformsInDoubles);
                double[] pdfGeoX = new double[1];
                double[] pdfGeoY = new double[1];
                //[地理坐标转换]
                gdal.ApplyGeoTransform(invGeoTransformsInDoubles, lng, lat, pdfGeoX, pdfGeoY);
                double[] elevation = new double[2];
                int readResult = rasterBand.ReadRaster((int) pdfGeoX[0], (int) pdfGeoY[0], 1, 1, gdalconst.GDT_CFloat64, elevation);
//                LogUtil.e(TAG, "getOfflineElevation elevation：" + Arrays.toString(elevation) + " readResult:" + readResult);
                dataset.delete();

                if (elevation[0] == -10000 && elevation[1] == 0) {
                    LogUtil.e(TAG, "当前点暂无有效高程数据(" + lat + "," + lng + ")");
                    return 0.0;
                }
                return elevation[0];
            }

        } catch (Exception ignored) {
        }
        return 0.0;
    }

    /**
     * 获取点的安全高度
     *
     * @param centerPoint 中心点坐标
     * @return
     */
    public Double getSecurityHeight(EfbLatLngPoint centerPoint) {
        try {
            Dataset dataset = gdal.Open(ConstantUtil.tifFilePath, gdalconstConstants.GA_ReadOnly);
            if (dataset == null) return 0.0;
            Driver driver = dataset.GetDriver();
            int RasterCount = dataset.getRasterCount();
//            LogUtil.e(TAG, "栅格数量：" + RasterCount);

            //[构造仿射变换矩阵]
            double[] gt = new double[6];
            dataset.GetGeoTransform(gt);
            double[] geoTransformsInDoubles = dataset.GetGeoTransform();
            Band rasterBand = dataset.GetRasterBand(1);
            double[] invGeoTransformsInDoubles = gdal.InvGeoTransform(geoTransformsInDoubles);
            double[] pdfGeoX = new double[1];
            double[] pdfGeoY = new double[1];

            //[坐标转换]
            gdal.ApplyGeoTransform(invGeoTransformsInDoubles, centerPoint.lng, centerPoint.lat, pdfGeoX, pdfGeoY);
            double[] tempElevations = new double[2];
            rasterBand.ReadRaster((int) pdfGeoX[0], (int) pdfGeoY[0], 1, 1, gdalconst.GDT_CFloat64, tempElevations);

            if (tempElevations[0] == -10000 && tempElevations[1] == 0) {
                LogUtil.e(TAG, "安全高度 ,当前点暂无有效高程数据(" + centerPoint.lat + "," + centerPoint.lng + ")");
                return 0.0;
            }

            //[对角距离为15km的矩形 待优化 可动态获取]
            int distance = (int) Math.sqrt(2 * 15000 * 15000);

            //[左上角右下角坐标]
            EfbLatLngPoint startPoint = TopoUtils.destinationPoint(centerPoint, distance, -45);
            EfbLatLngPoint endPoint = TopoUtils.destinationPoint(centerPoint, distance, 135);

            //[经纬度转换为栅格像素坐标]
            int[] startRow = ApplyGeoTransform(gt, startPoint.lng, startPoint.lat);
            int[] endRow = ApplyGeoTransform(gt, endPoint.lng, endPoint.lat);

            //[判断是否坐标超出范围 ???方法不准]
            if (startRow[0] < 0 || startRow[1] < 0 || startRow[0] > dataset.getRasterXSize() || startRow[1] > dataset.getRasterYSize()) {
//                LogUtil.e(TAG, "安全高度 if：" + Arrays.toString(startRow) + "坐标值超出栅格范围！");
                return tempElevations[0];
            } else if (endRow[0] < 0 || endRow[1] < 0 || endRow[0] > dataset.getRasterXSize() || endRow[1] > dataset.getRasterYSize()) {
//                LogUtil.e(TAG, "安全高度 else if：" + Arrays.toString(startRow) + "坐标值超出栅格范围！");
                return tempElevations[0];
            }

            //[遍历珊瑚格]
            ArrayList<Double> elevationList = new ArrayList<>();
            for (int i = 0; i < dataset.getRasterCount(); i++) {
                Band band = dataset.GetRasterBand(i + 1);
                int subX = endRow[0] - startRow[0];
                int subY = endRow[1] - startRow[1];
                double[] tempArrays = new double[subX * subY];
                int readResult = band.ReadRaster(startRow[0], startRow[1], subX, subY, tempArrays);
                for (double item : tempArrays) {
                    elevationList.add(item);
                }
            }
            double securityHeight = Collections.max(elevationList);
            //LogUtil.e(TAG, "安全高度 securityHeight：" + securityHeight + " ,tempElevation:" + tempElevations[0]);

            //[实际结果处理 小于200加200m 否则加300m]
            if (securityHeight < 200) {
                securityHeight += 200;
            } else {
                securityHeight += 300;
            }
            dataset.delete();

            return securityHeight;
        } catch (Exception ignored) {
        }
        return 0.0;
    }

    //[地图坐标转换为单个像素坐标]
    private int[] ApplyGeoTransform(double[] gt, double X, double Y) {
        int[] ints = new int[2];
        double dfLine = Math.floor(((Y - gt[3]) * gt[1] - (X - gt[0]) * gt[4]) / (gt[5] * gt[1] - gt[2] * gt[4]));
        double dfPixel = Math.floor((X - gt[0] - dfLine * gt[2]) / gt[1]);
        ints[0] = Double.valueOf(dfPixel).intValue();
        ints[1] = Double.valueOf(dfLine).intValue();
        return ints;
    }


    /**
     * 批量读取高程数据, 避免重复open高程文件 浪费性能
     *
     * @param latLngPoints
     * @return
     */
    public List<Double> getOfflineElevation(List<EfbLatLngPoint> latLngPoints) {
        List<Double> elevations = new ArrayList<>();
        try {
            Dataset dataset = gdal.Open(ConstantUtil.tifFilePath, gdalconstConstants.GA_ReadOnly);
            if (dataset != null) {

                for (int i = 0; i < latLngPoints.size(); i++) {
                    EfbLatLngPoint latLngPoint = latLngPoints.get(i);

                    double[] gt = new double[6];
                    dataset.GetGeoTransform(gt);

                    double[] geoTransformsInDoubles = dataset.GetGeoTransform();
                    Band rasterBand = dataset.GetRasterBand(1);

                    double lat = latLngPoint.lat;
                    double lng = latLngPoint.lng;

                    double[] invGeoTransformsInDoubles = gdal.InvGeoTransform(geoTransformsInDoubles);
                    double[] pdfGeoX = new double[1];
                    double[] pdfGeoY = new double[1];
                    gdal.ApplyGeoTransform(invGeoTransformsInDoubles, lng, lat, pdfGeoX, pdfGeoY);
                    double[] elevation = new double[2];
                    int readResult = rasterBand.ReadRaster((int) pdfGeoX[0], (int) pdfGeoY[0], 1, 1, gdalconst.GDT_CFloat64, elevation);
                    LogUtil.e(TAG, "i == " + i + " getOfflineElevation elevation：" + Arrays.toString(elevation) + " readResult:" + readResult);

                    if (elevation[0] == -10000 && elevation[1] == 0) {
                        LogUtil.e(TAG, "当前点暂无有效高程数据(" + lat + "," + lng + ")");
                        elevations.add(0.0);
                    } else {
                        elevations.add(elevation[0]);
                    }
                }

                dataset.delete();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return elevations;
    }


}
